from world_object import WorldObject
from World import string, world, life, text, score
from math import sqrt, cos, sin, pi, radians
import pygame
import logging
from random import Random
import constants
import jukebox
import os

class Character(WorldObject):
    """Understands the representation of the player in the game world"""

    JUMP_LEFT = 0
    JUMP_RIGHT = 1
    CLIMB_LEFT = 2
    CLIMB_RIGHT = 3
    STRAFE_OFFSET = 7

    def __init__(self, x=30, y=30):
        WorldObject.__init__(self, x, y)
        self.load_sprites(('dog_jump_left.png', 'dog_jump_right.png', 'dog_climb_left.png', 'dog_climb_right.png'))
        self.image = self.sprites[Character.JUMP_RIGHT]
        self._life = life.Life(constants.FULL_LIFE)
        self._score = score.Score()
        self._string = None
        self._dx = 0
        self._dy = 0
        self._invincible_time_left = 0
        self._height = 0
        self._repositioning = False
        self.world = None
        self._strings_last_met = []
        self.z_index = 10
        self._climbing_sprites = [self.sprites[Character.CLIMB_LEFT], self.sprites[Character.CLIMB_RIGHT]]
        self._engine = None

    def make_invincible(self, seconds=3):
        self._invincible_time_left = seconds * constants.FRAME_RATE

    def points(self):
        return self._score.num()

    def score(self):
        return self._score

    def climb_up(self, steps=constants.CLIMB_SPEED):
        logging.getLogger(constants.LOGGER_NAME).debug("climbing up with speed "+ str(steps))
        self.change_to_random_climb_sprite()
        self.attach_to_highest_string_met()
        if self._string and self.is_below(*self._string.position()):
            self.y -= steps
            self._height += steps

    def change_to_random_climb_sprite(self):
        new_image = Random().randint(Character.CLIMB_LEFT, Character.CLIMB_RIGHT)
        self.change_to_sprite(new_image)

    def climb_down(self, steps=constants.CLIMB_SPEED):
        self.change_to_random_climb_sprite()
        self.attach_to_highest_string_met()
        if self._string:
            self.y += steps
            self._height -= steps
            if self.is_below(self._string.x, self._string.y+self._string.length()):
                self.release_string()

    def attach_to_highest_string_met(self):
        if not self._strings_last_met: return
        highest_string = reduce(string.String.highest_string, self._strings_last_met)
        if highest_string:
            self.grab_string(highest_string)

    def attach_to_lowest_string_met(self):
        if not self._strings_last_met: return
        lowest_string = reduce(string.String.lowest_string, self._strings_last_met)
        if lowest_string:
            self.grab_string(lowest_string)

    def jump_left(self, left=True, angle=constants.JUMP_ANGLE, speed=constants.JUMP_SPEED):
        logging.getLogger(constants.LOGGER_NAME).debug("jumping with speed "+ str(speed))
        if self._string:
            old_string = self._string
            self.release_string()
            speedup_on_a_string = 2
            if not self.is_on_string(): speedup_on_a_string = 0
            if left: self.x -= Character.STRAFE_OFFSET
            else: self.x += Character.STRAFE_OFFSET
            if left: self._dx = -1 * speed * cos(radians(angle))
            else: self._dx = +1 * speed * cos(radians(angle))
            self._dy = -1 * speed * sin(radians(angle))
            if not self.overlaps(old_string):
                jukebox.juke.play_sound(constants.JUMP_SOUND)
        else:
            if left: self.change_to_sprite(Character.JUMP_LEFT)
            else: self.change_to_sprite(Character.JUMP_RIGHT) 

    def jump_right(self, angle=constants.JUMP_ANGLE, speed=constants.JUMP_SPEED):
        logging.getLogger(constants.LOGGER_NAME).debug("jumping right")
        self.jump_left(left=False, angle=angle, speed=speed)

    def is_on_string(self):
        if self._string:
            if self._string.length() <= 0: return False
            if not self._string.overlaps(self): return False
            return True
        return False

    def tick_update(self):
        self._score.update(self._string, self.height())
        if self._invincible_time_left > 0: self._invincible_time_left -= 1
        if not self.is_on_string():
            self.x += self._dx
            self.y += self._dy
            self.do_the_pacman()
            self._dy += constants.GRAVITY
        else:
            if self.image not in self._climbing_sprites:
                self.change_to_sprite(Character.CLIMB_LEFT)

    def do_the_pacman(self):
        if self._engine:
            total_width = self._engine.get_screen().get_width()
            if self.x <= 0: self.x = total_width
            elif self.x >= total_width: self.x = 0
    
    def update_from_observable(self, event=None):
        if event is None:
            return
        if event.key == pygame.K_UP:
            self.climb_up(constants.CLIMB_SPEED)
        if event.key == pygame.K_DOWN:
            self.climb_down(constants.CLIMB_SPEED)
        if event.key == pygame.K_LEFT:
            self.jump_left(speed=constants.JUMP_SPEED)
        if event.key == pygame.K_RIGHT:
            self.jump_right(speed=constants.JUMP_SPEED)
        if self.is_repositioning():
            self.reposition()

    def grab_string(self, rope):
        self._string = rope 
        self._dx = 0
        self._dy = 0

    def release_string(self):
        self._string = None

    def before_collisions(self):
        self._strings_last_met = []
        self.release_string()

    def collide(self, other):
        if type(other) == string.String:
            self._strings_last_met.append(other)
            if not self.is_on_string():
                self.grab_string(other)

    def is_below(self, x, y):
        return self.y > y

    def is_above(self, x, y):
        return self.y < y

    def hit(self):
        if self._invincible_time_left <= 0:
            self._hit()
            jukebox.juke.play_sound(constants.OUCH_SOUND)
            self._invincible_time_left = constants.INVINCIBLE_TIME * constants.FRAME_RATE

    def _hit(self):
        self._life.hit()
        self._repositioning = True
        if self.is_dead():
            self._repositioning = False
            self.notify(death=True)
        else:
            self.notify(reposition=True)

    def is_dead(self):
        return self._life.is_over()
        
    def is_repositioning(self):
        if self._repositioning: return True
        return False
        
    def reposition(self):
        if(self.is_repositioning()):
            a_string = string.String(150, 150, 60)
            if self.world != None: self.world.add_object(a_string)
            self.set_position(150, 150)
            self.grab_string(a_string)
            self._repositioning = False
        
    def life(self):
        return self._life

    def set_engine(self, engine):
        self._engine = engine
        
    def set_world(self, world):
        self.world = world
        
    def height(self):
        return self._height
        
    def __str__(self):
        return "character %s" % WorldObject.__str__(self)
    
